/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "MultiFile.h"
#include "File.h"
#include "Memory.h"
#include <NoCopy.h>
#include <filesystem/File.h>
#include <logs/Log.h>
#include <Gluck.h>
#include <vector>

static filesystem::File * archive = 0;
static std::vector<char> unpack_result;

unsigned long UnpPackedSize;
unsigned long UnpFileCRC;
long DestUnpSize;
int Encryption;


namespace liba
{
namespace resource
{

#ifndef WIN_VERSION
	
	MultiFile::MultiFile(const std::string & password)
	{}
	
	MultiFile::~MultiFile()
	{}
	
	bool MultiFile::find(filesystem::Finder * finder, const filesystem::String & base_path)
	{
		return false;
	}
	
	void MultiFile::add_multifile(const filesystem::String & name)
	{}
	
	Resource * MultiFile::create_resource(const filesystem::String & name)
	{
		return 0;
	}
	
	bool MultiFile::ExtractCurrentFile(Archive &Arc,int HeaderSize,bool &Repeat)
	{
		return false;
	}
	
#else

int CALLBACK MyCallback(UINT msg,LONG UserData,LONG P1,LONG P2)
{
	switch (msg)
	{
	case UCM_PROCESSDATA:
		{
			int old_size = unpack_result.size();
			unpack_result.resize(old_size + P2);
			char * dst = (char *)&(unpack_result.front());
			memcpy(dst+old_size, (char*)P1, P2);
		}
		return(0);
	}
	return(0);
}

void UnstoreFile(ComprDataIO &DataIO, Int64 DestUnpSize)
{
	Array<byte> Buffer(0x10000);
	while (1)
	{
		unsigned int Code=DataIO.UnpRead(&Buffer[0],Buffer.Size());
		if (Code==0 || (int)Code==-1)
			break;
		Code=Code<DestUnpSize ? Code:int64to32(DestUnpSize);
		DataIO.UnpWrite(&Buffer[0],Code);
		if (DestUnpSize>=0)
			DestUnpSize-=Code;
	}
}

bool MultiFile::ExtractCurrentFile(Archive &Arc,int HeaderSize,bool &Repeat)
{
	bool SignatureFound = false;

	int HeadType=Arc.GetHeaderType();
	if (HeadType!=FILE_HEAD)
	{
		if (HeadType==AV_HEAD || HeadType==SIGN_HEAD)
			SignatureFound=true;
		if (HeadType==NEWSUB_HEAD)
		{
			if (Arc.SubHead.CmpName(SUBHEAD_TYPE_AV))
				SignatureFound=true;
		}
		if (HeadType==ENDARC_HEAD)
			if (Arc.EndArcHead.Flags & EARC_NEXT_VOLUME)
			{
				Arc.Seek(Arc.CurBlockPos,SEEK_SET);
				return(true);
			}
			else
				return(false);
		Arc.SeekToNext();
		return(true);
	}

	if(SignatureFound)
		return false;

	filesystem::String filename = Arc.NewLhd.FileName;

	if (Arc.IsArcLabel())
		return(true);

	bool is_dir = Arc.IsArcDir();
	if(is_dir)
	{
		folder_list.push_back(filename);
	}
	else
	{
		ArchiveData arc_data;
		arc_data.arc = &Arc;
		arc_data.pos = Arc.CurBlockPos;

		impls.insert(std::make_pair(filename, arc_data));
	}

	Arc.Seek(Arc.NextBlockPos-Arc.NewLhd.FullPackSize,SEEK_SET);

	bool password = Arc.NewLhd.Flags & LHD_PASSWORD;

	if (Arc.NewLhd.UnpVer<13 || Arc.NewLhd.UnpVer>UNP_VER)
	{
		logs::log() << logs::error << "MultiFile::add_multifile() archive compression version is greater than expected\n";
		return false;
	}

/*	RAROptions *Cmd = Arc.GetRAROptions();
	Cmd->Callback = MyCallback;
	Cmd->DllOpMode = RAR_TEST;

	ComprDataIO DataIO;
	DataIO.CurUnpRead=0;
	DataIO.CurUnpWrite=0;
	DataIO.UnpFileCRC=Arc.OldFormat ? 0 : 0xffffffff;
	DataIO.PackedCRC=0xffffffff;
	DataIO.SetEncryption(
		(Arc.NewLhd.Flags & LHD_PASSWORD) ? Arc.NewLhd.UnpVer:0, password.c_str(),
		(Arc.NewLhd.Flags & LHD_SALT) ? Arc.NewLhd.Salt:NULL,false);
	DataIO.SetPackedSizeToRead(Arc.NewLhd.FullPackSize);
	DataIO.SetFiles(&Arc,0);
	DataIO.SetTestMode(false);
	DataIO.SetSkipUnpCRC(false);

	Unpack * Unp=new Unpack(&DataIO);
	Unp->Init(NULL);

	if (Arc.NewLhd.Method==0x30)
	{
		UnstoreFile(DataIO, Arc.NewLhd.FullUnpSize);
	}
	else
	{
		Unp->SetDestSize(Arc.NewLhd.FullUnpSize);
		if (Arc.NewLhd.UnpVer<=15)
			Unp->DoUnpack(15, true && Arc.Solid);
		else
			Unp->DoUnpack(Arc.NewLhd.UnpVer,Arc.NewLhd.Flags & LHD_SOLID);
	}

	delete Unp; Unp = 0;
*/
	if (Arc.IsOpened())
		Arc.SeekToNext();

	return true;
}


void MultiFile::add_multifile(const filesystem::String & name)
{
	logs::log() << "MultiFile::add_multifile() {" << name << "}\n";

	Archive * archive = new Archive(0);
	archive->Open(name.c_str());
	if(!archive->IsArchive(false))
		return;

	archives.push_back(archive);

	while (1)
	{
		int Size = archive->ReadHeader();
		bool Repeat=false;
		if (!ExtractCurrentFile(*archive, Size, Repeat))
			if (Repeat)
			{
				return;
			}
			else
				break;
	}
}

Resource * MultiFile::create_resource(const filesystem::String & name)
{
	LoadedImplMap::const_iterator it = loaded_impls.find( name );
	if( it != loaded_impls.end() )
		return new Memory( it->second );
	ImplMap::const_iterator it2 = impls.find( name );
	if( it2 == impls.end() )
		return 0;

	threads::Lock lock(mutex);

	unpack_result.clear();

	ArchiveData arc_data = it2->second;
	Archive & Arc = *arc_data.arc;
	Arc.Seek(arc_data.pos, SEEK_SET);
	Arc.ReadHeader();

	Arc.Seek(Arc.NextBlockPos-Arc.NewLhd.FullPackSize,SEEK_SET);

	bool encrypted = Arc.NewLhd.Flags & LHD_PASSWORD;

	if (Arc.NewLhd.UnpVer<13 || Arc.NewLhd.UnpVer>UNP_VER)
	{
		logs::log() << logs::error << "MultiFile::add_multifile() archive compression version is greater than expected\n";
		return false;
	}

	RAROptions *Cmd = Arc.GetRAROptions();
	Cmd->Callback = MyCallback;
	Cmd->DllOpMode = RAR_TEST;

	ComprDataIO DataIO;
	DataIO.CurUnpRead=0;
	DataIO.CurUnpWrite=0;
	DataIO.UnpFileCRC=Arc.OldFormat ? 0 : 0xffffffff;
	DataIO.PackedCRC=0xffffffff;
	DataIO.SetEncryption(
		(Arc.NewLhd.Flags & LHD_PASSWORD) ? Arc.NewLhd.UnpVer:0, (char *)password.c_str(),
		(Arc.NewLhd.Flags & LHD_SALT) ? Arc.NewLhd.Salt:NULL,false);
	DataIO.SetPackedSizeToRead(Arc.NewLhd.FullPackSize);
	DataIO.SetFiles(&Arc,0);
	DataIO.SetTestMode(false);
	DataIO.SetSkipUnpCRC(false);

	Unpack * Unp=new Unpack(&DataIO);
	Unp->Init(NULL);

	if (Arc.NewLhd.Method==0x30)
	{
		UnstoreFile(DataIO, Arc.NewLhd.FullUnpSize);
	}
	else
	{
		Unp->SetDestSize(Arc.NewLhd.FullUnpSize);
		if (Arc.NewLhd.UnpVer<=15)
			Unp->DoUnpack(15, true && Arc.Solid);
		else
			Unp->DoUnpack(Arc.NewLhd.UnpVer,Arc.NewLhd.Flags & LHD_SOLID);
	}

	delete Unp; Unp = 0;

	return new Memory(unpack_result);
}

bool MultiFile::find(filesystem::Finder * finder, const filesystem::String & base_path)
{
	filesystem::String good_base_path = base_path + "/";
	FolderList::const_iterator fit = folder_list.begin();
	for( ;fit != folder_list.end(); ++fit )
	{
		if( fit->trim_file_name() + "/" == good_base_path ) // process empty base_folder also
			if( !finder->on_folder( *fit ) )
				return false;
	}

	LoadedImplMap::const_iterator it = loaded_impls.begin();
	for( ;it != loaded_impls.end(); ++it )
	{
		if( it->first.trim_file_name() + "/" == good_base_path ) // process empty base_folder also
			if( !finder->on_file( it->first ) )
				return false;
	}
	ImplMap::const_iterator it2 = impls.begin();
	for( ;it2 != impls.end(); ++it2 )
	{
		if( it2->first.trim_file_name() + "/" == good_base_path ) // process empty base_folder also
			if( !finder->on_file( it2->first ) )
				return false;
	}
	return true;
}

MultiFile::MultiFile(const std::string & password)
	: password( password )
{}

MultiFile::~MultiFile()
{}

#endif
	
} // namespace resource
} // namespace liba

